<?php

declare(strict_types=1);

namespace App\CustHub\Service;

use App\Chat\Event\TalkMessageRevokeEvent;
use App\Chat\Event\TalkRecordEvent;
use App\CustHub\Amqp\Producers\CommonTaskCenterProducers;
use App\CustHub\Constants\MsgTypeConstants;
use App\CustHub\Dto\CommonProducersDto;
use App\CustHub\Event\AutoKickTrigger;
use App\CustHub\Event\RobotInfoChange;
use App\CustHub\Event\RoomBroadcastTrigger;
use App\CustHub\Event\CtrlRoomTrigger;
use App\CustHub\Event\SmartAnswerTrigger;
use App\CustHub\Mapper\ChChatRecordMapper;
use App\CustHub\Mapper\ChRobotMapper;
use App\CustHub\Model\ChContact;
use App\CustHub\Model\ChMaterial;
use App\CustHub\Model\ChRobot;
use App\Robot\Service\RobotMsgService;
use Carbon\Carbon;
use FriendsOfHyperf\Lock\Exception\LockTimeoutException;
use Hyperf\Amqp\Producer;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Event\EventDispatcher;
use Mine\Helper\Str;
use Psr\EventDispatcher\EventDispatcherInterface;
use function FriendsOfHyperf\Lock\lock;
use function Hyperf\Collection\data_get;
use function Hyperf\Coroutine\co;
use function Hyperf\Support\optional;

class NotifyService
{
    #[Inject]
    protected ChRobotMapper $robotMapper;

    #[Inject]
    private EventDispatcherInterface $eventDispatcher;


    private const ADD_KEY = 'robot_add_key:';

    public function sync(array $data)
    {
        stdout_log()->info('接收到消息：'.json_encode($data,JSON_UNESCAPED_UNICODE));

        $robotInfo = null;
        $data['user_id'] = (string)$data['user_id'];
        isset($data['sender']) && $data['sender'] = (string)$data['sender'];
        isset($data['receiver']) && $data['receiver'] = (string)$data['receiver'];
        isset($data['from_room_id']) && $data['from_room_id'] = (string)$data['from_room_id'];

//        if($data['notice_type'] == 11001 || $data['notice_type'] == 11000){
//            $customerParam = $data['customer_param'];
//            $customerParamArr = explode('_',$customerParam);
//            $mainUid = $customerParamArr[0];
//            $robotInfo = $this->robotMapper->findByUserId($data['user_id'],(int)$mainUid);
//        }elseif(!empty($data['clientKey'])){
//            $robotInfo = $this->robotMapper->findByRobotKey($data['clientKey']);
//        }

        $robotInfo = $this->robotMapper->findByRobotKey($data['clientKey']);

        if($data['notice_type'] != 11000 && empty($robotInfo)){
            return;
        }

        switch ($data['notice_type']){
            case 11000:
                if(!empty($robotInfo)){
                    $robotInfo->robot_key = $data['clientKey'];
                    $robotInfo->save();
                    $this->robotMapper->clearCache($robotInfo->id,$data['clientKey']);
                }
                //扫码登录通知 五分钟
                $rst = redis()->set("robot_login_result:".$data['login_qrcode_key'],$data,60 * 60);
                break;
            case 11001:
                //登录成功通知
                container()->get(RobotLoginService::class)->loginSuccess($robotInfo,$data['clientKey'],$data['uuid']);
                break;
            case 11002:
                if(isset($data['logout_type']) && $data['logout_type'] == 3){
                    //心跳失败 自动登录
                    $params = [
                        'main_uid' => $robotInfo->main_uid,
                        'id' => $robotInfo->id,
                    ];
                    try {
                        container()->get(RobotLoginService::class)->autoLogin($params);
                    }catch (\Throwable $throwable){
                        stdout_log()->error('自动登录失败:'.$robotInfo->id.'-errmsg:'.$throwable->getMessage());
                        container()->get(RobotLoginService::class)->loginOutNotify($robotInfo);
                    }
                }else{
                    container()->get(RobotLoginService::class)->loginOutNotify($robotInfo);
                }
                //账号退出登录通知
                break;
            case 104012:
            case 15000:
                //消息通知
                $this->handleMessage($data,$robotInfo);
                break;
            case 15001:
                //群相关
                $this->handleRoom($data,$robotInfo);
                break;
            case 15500:
                //【置顶/免打扰/保存群到通信录】消息通知
                $this->handleSystemNotification($data,$robotInfo);
                break;
            default:
                break;
        }
    }

    private function handleMessage(array $data,ChRobot $robotInfo): void
    {
        $msgtype = data_get($data,'msgtype');
        $data = $this->changeData($data,$robotInfo);
        $content = $data['content'] ?? '';
        $msgData = $data['msg_data'] ?? [];

        //是否个微用户发送的消息
        $isRobot = $data['user_id'] == $data['sender'];
        $data['is_robot'] = $isRobot;

        $msg_type = '';
        $needSaveChatRecord = false;
        switch ($msgtype){
            case 0:
            case 2:
                $needSaveChatRecord = true;
                //我是一条文本消息
                if(!empty($data['at_list'])){
                    $content = $this->parseContent($data['content'],$data['at_list']);
                    $msg_type = MsgTypeConstants::MIXED_TEXT;
                }else{
                    $msg_type = MsgTypeConstants::TEXT;
                }
                if($data['is_room'] == 0){
                    if(!$this->checkAddFriendKey($data)){
                        container()->get(ContactReceiveCallBackService::class)->messageHandler($data,$robotInfo);
                    }
                    container()->get(ChRoomKeywordService::class)->getMeetTheConditionScheme([
                        'robot_user_id' => $data['user_id'],
                        'user_id' => $msgtype == 0 ? $data['receiver'] : $data['sender'],
                        'content' => $data['content'],
                        'is_robot' => $data['is_robot'],
                        'robotInfo' => $robotInfo->toArray(),
                    ]);
                    container()->get(ChKeywordTagSchemeService::class)->triggerScheme($data,$robotInfo);
                }else{
                    //群消息
                    container()->get(RoomReceiveCallBackService::class)->roomMessage($robotInfo,$data);

                    //中控群调试
                    $data['isWin'] = 1;
                    event(new CtrlRoomTrigger($robotInfo,$data,1));
                }
                break;
            case 4:
                //聊天合集
                $needSaveChatRecord = true;
                break;
            case 14:// 图片
            case 101:
                //我是一条图片消息
                $msg_type = MsgTypeConstants::IMAGE;
                $needSaveChatRecord = true;
                $content = container()->get(RobotMsgService::class)->processMessages($robotInfo,$msg_type,$data);
                if($data['is_room']){
                    $data['content'] = $content;
                    event(new CtrlRoomTrigger($robotInfo,$data,2));
                }
                break;
            case 23:// 视频
            case 103:
                //我是一条视频消息
                $msg_type = MsgTypeConstants::VIDEO;
                $needSaveChatRecord = true;
                $content = container()->get(RobotMsgService::class)->processMessages($robotInfo,$msg_type,$data);
                break;
            case 15:// 文件
            case 102:
                //我是一条文件消息
                $msg_type = MsgTypeConstants::FILE;
                $msgData['file_name'] = base64_decode($msgData['file_name']);
                $content = container()->get(RobotMsgService::class)->processMessages($robotInfo,$msg_type,$data);
                $needSaveChatRecord = true;
                break;
            case 29:// GIF
            case 104:
                //我是一条GIF消息
                $msg_type = MsgTypeConstants::EMOJI;
                $content = container()->get(RobotMsgService::class)->processMessages($robotInfo,$msg_type,$msgData);
                $needSaveChatRecord = true;
                break;
            case 20:// 大文件消息 >20M
                //我是一条大文件消息
                $needSaveChatRecord = true;
                $msgData['file_name'] = base64_decode($msgData['file_name']);
                break;
            case 22:// 大视频 >20M
                //我是大视频消息
                $needSaveChatRecord = true;
                break;
            case 6:// 位置
                //我是一条位置消息
                $needSaveChatRecord = true;
                $msgData['title'] = base64_decode($msgData['title']);
                $msgData['address'] = base64_decode($msgData['address']);
                break;
            case 13:// 链接
                //我是一条链接消息
                $msg_type = MsgTypeConstants::URL;
                $needSaveChatRecord = true;
                break;
            case 41:// 名片
                $needSaveChatRecord = true;
                $msgData['corp_name'] = base64_decode($msgData['corp_name']);
                $msgData['nickname'] = base64_decode($msgData['nickname']);
                $msgData['real_name'] = base64_decode($msgData['real_name']);
                break;
            case 26:// 红包
                break;
            case 16:// 语音
                break;
            case 78:// 小程序
                $needSaveChatRecord = true;
//                $isPersonal = $data['user_id'] != $data['sender'] ? 1 : 0;
                $isPersonal = 0;
                $data['msg_data']['title'] = $msgData['title'] = base64_decode($msgData['title']);
                $data['msg_data']['desc'] = $msgData['desc'] = base64_decode($msgData['desc']);

                //$this->saveMiniProgram($robotInfo,$data['msg_data'],$isPersonal);
                $msg_type = MsgTypeConstants::MINI_PROGRAM;
                break;
            case 123:// 图文混合
                $needSaveChatRecord = true;
                break;
            case 146:// 直播
                break;
            case 141:
                $needSaveChatRecord = true;
                //视频号
                //插入素材库
//                $isPersonal = $data['user_id'] != $data['sender'] ? 1 : 0;
                $isPersonal = 0;
                $msgData['channel_name'] = base64_decode($msgData['channel_name']);
                //$this->saveVideoChannel($robotInfo,$data['msg_data'],$isPersonal);
                $msg_type = MsgTypeConstants::VIDEO_CHANNEL;
                break;
            case 213:
                //群接龙
                $needSaveChatRecord = true;
                break;
            case 2001:// 消息已读通知
                break;
            case 2005:// 消息未读
                break;
            case 1011://需要添加好友
                if(isset($data['msg_data']['base64_raw_data'])){
                    //添加好友处理
                    redis()->set($this->getAddFriendKey($data),1,30);
                    $data['content'] = base64_decode($data['msg_data']['base64_raw_data']);
                    container()->get(ContactReceiveCallBackService::class)->messageHandler($data,$robotInfo);
                }
                break;
            case 1052:
                //设置置顶
                break;
            case 1055:
                //取消置顶
                break;
            case 2118:
                //群信息变更通知
                if($data['is_room'] == 1 && !$this->isRoomNotificationProcessed($data['from_room_id'])){
                    container()->get(RoomReceiveCallBackService::class)->syncRoomHandler($data,$robotInfo);
                }
                break;
            case 2063:
                //撤回消息
                if($data['is_room']){
                    event(new AutoKickTrigger($robotInfo,$data,1));
                }

                $revoke_msg_unique_identifier = data_get($msgData,'revoke_appinfo');
                stdout_log()->info('撤回消息：'.$revoke_msg_unique_identifier);
                if(!empty($revoke_msg_unique_identifier)){
                    event(new TalkMessageRevokeEvent($robotInfo->id,$revoke_msg_unique_identifier));
                }

                break;
        }

        if($needSaveChatRecord){
            $data['robot_id'] = $robotInfo->id;
            $data['msg_data'] = $msgData;
            $data['content'] = [
                'content' => $content,
                'msg_type'=> $msg_type
            ];
            $chatRecordMapper = container()->get(ChChatRecordMapper::class);
            $chatRecordId = $chatRecordMapper->save($data);

            if($data['sender'] != $data['user_id'] && $data['is_room'] == 0){
                //记录客户最后一次发言时间
                ChContact::query()
                    ->where('robot_id',$robotInfo->id)
                    ->where('user_id',$data['sender'])
                    ->update([
                        'last_send_msg_time' => Carbon::now()->toDateTimeString()
                    ]);
            }

            if($data['is_room']){
                event(new AutoKickTrigger($robotInfo,$data));
                //群转播
                event(new RoomBroadcastTrigger($robotInfo,$data));
            }

            if($msg_type == MsgTypeConstants::TEXT || $msg_type == MsgTypeConstants::IMAGE || $msg_type == MsgTypeConstants::MIXED_TEXT){
                //触发智能应答
                co(function ()use($data,$msg_type){
                    $this->eventDispatcher->dispatch(new SmartAnswerTrigger($data));
                });
            }

            if(!$isRobot){
                $data['chat_record_id'] = $chatRecordId;
                event(new CtrlRoomTrigger($robotInfo,$data));
            }
            event(new TalkRecordEvent($robotInfo,$data));
        }
    }

    private function getAddFriendKey(array $data): string
    {
        return self::ADD_KEY. md5($data['sender'].$data['receiver']);
    }

    private function checkAddFriendKey(array $data): bool
    {
        return redis()->exists($this->getAddFriendKey($data)) > 0;
    }

    /**
     * 群相关
     * @param array $data
     * @param ChRobot $robotInfo
     * @return void
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    private function handleRoom(array $data,ChRobot $robotInfo): void
    {
        $cacheKey='room_notice_handle_set:'.$data['msg_id'];
        if(redis()->exists($cacheKey)){
            return;
        }
        $data = $this->changeData($data,$robotInfo);
        if(isset($data['room_op_type'])){
            $lock = lock('room_notice_handle_lock'.$data['from_room_id'],  4);
            try {
                $lock->block(3);
                $this->markRoomNotificationAsProcessed($data['from_room_id']);
                redis()->set($cacheKey,1,30);
                container()->get(RoomReceiveCallBackService::class)->roomOp($data,$robotInfo);
            } catch (LockTimeoutException $e) {
                // 无法获取锁...
            } finally {
                optional($lock)->release();
            }
        }
    }

    private function isRoomNotificationProcessed(mixed $from_room_id): bool
    {
        $r= redis()->exists('processed_room_notification:' . $from_room_id);
        return $r > 0;
    }

    private function markRoomNotificationAsProcessed(mixed $from_room_id): void
    {
        redis()->set('processed_room_notification:' . $from_room_id, $from_room_id,5);
    }


    private function handleSystemNotification(array $data,ChRobot $robotInfo)
    {
        if($data['msg_type'] == 2115){
            container()->get(RoomReceiveCallBackService::class)->manageLocalContacts($robotInfo);
        }elseif ($data['msg_type'] == 2114){
            //企微号信息变更
            event(new RobotInfoChange($robotInfo));
        }else{
            container()->get(ContactReceiveCallBackService::class)->handle($data,$robotInfo);
        }
    }

    /**
     * 保存视频号素材
     * @param ChRobot $robotInfo
     * @param array $msgData
     * @param int $isPersonal
     * @return void
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    private function saveVideoChannel(ChRobot $robotInfo,array $msgData,int $isPersonal): void
    {
        $msgData['username'] = base64_decode($msgData['username']);
        $msgData['channel_name'] = base64_decode($msgData['channel_name']);
        $data = [
            'title' => '未命名素材',
            'content' =>  [
                [
                    'msg_type' => 'video_channel',
                    'content' => $msgData,
                    'extra' => []
                ]
            ],
            'is_personal' => $isPersonal
        ];
        ChMaterial::saveMaterial($robotInfo->uid,$robotInfo->main_uid,$data);
    }

    /**
     * 保存小程序素材
     * @param ChRobot $robotInfo
     * @param array $msgData
     * @param int $isPersonal
     * @return void
     * @throws \Psr\Container\ContainerExceptionInterface
     * @throws \Psr\Container\NotFoundExceptionInterface
     */
    private function saveMiniProgram(ChRobot $robotInfo,array $msgData,int $isPersonal): void
    {
        $data = [
            'title' => '未命名素材',
            'content' =>  [
                [
                    'msg_type' => 'mini_program',
                    'content' => $msgData,
                    'extra' => []
                ]
            ],
            'is_personal' => $isPersonal
        ];
        ChMaterial::saveMaterial($robotInfo->uid,$robotInfo->main_uid,$data);
    }


    private function changeData($data,ChRobot $robotInfo): array
    {

        $data['uid'] = $robotInfo->main_uid;
        $data['robot_id'] = $robotInfo->id;

        if(!empty($data['at_list'])){
            foreach ($data['at_list'] as $ak=>$aItem){
                $data['at_list'][$ak]['nickname'] = base64_decode($aItem['nickname']);
            }
        }
        $newData = [];
        foreach ($data as $k=>$item){
            $newData[Str::snake($k)] = $item;
        }
        return $newData;
    }

    /**
     * 解析文本消息，提取 @ 昵称并转换为数组
     * @param string $content
     * @param array $atList
     * @return array[]
     */
    public function parseContent(string $content, array $atList): array {
        $items = [];
        $pos = 0; // 当前处理位置

        // 依次处理每个 at_list 中的 nickname
        foreach ($atList as $at) {
            $nickname = $at['nickname'];
            $atTag = '@' . $nickname;

            // 以 @nickname 作为分割点来查找匹配的内容
            $startPos = strpos($content, $atTag, $pos);
            if ($startPos !== false) {
                // 处理 @nickname 之前的文本
                if ($startPos > $pos) {
                    $items[] = [
                        'text' => substr($content, $pos, $startPos - $pos),
                        'type' => 'text'
                    ];
                }

                // 添加 @nickname 的结构化对象
                $items[] = [
                    'at_id' => $at['user_id'],
                    'at_name' => $nickname,
                    'text' => '',
                    'type' => '@'
                ];

                // 更新处理位置，跳过当前的 @nickname
                $pos = $startPos + strlen($atTag);

                // 处理 @nickname 后面跟着的其他文本
                $nextAtPos = strpos($content, '@', $pos);
                if ($nextAtPos !== false) {
                    $items[] = [
                        'text' => substr($content, $pos, $nextAtPos - $pos),
                        'type' => 'text'
                    ];
                    $pos = $nextAtPos;
                } else {
                    // 处理没有下一个 @ 时剩余的所有文本
                    if ($pos < strlen($content)) {
                        $items[] = [
                            'text' => substr($content, $pos),
                            'type' => 'text'
                        ];
                    }
                    break;
                }
            }
        }

        // 如果处理到最后还剩余未匹配的文本
        if ($pos < strlen($content)) {
            $items[] = [
                'text' => substr($content, $pos),
                'type' => 'text'
            ];
        }

        return $items;
    }
}